﻿using System;
using System.Collections.Generic;
using PostSharp.Aspects;
using Sapit.Attributes.Interfaces;
using Sapit.Helpers;

namespace Sapit.PostSharp.Attributes
{
    /// <summary>
    /// Apply this attribute to methods that should be cached.
    /// </summary>
    [Serializable]
    public class CacheAttribute : OnMethodBoundaryAspect, ICache
    {
        /// <summary>
        /// Number of seconds to cache the result of the method
        /// </summary>
        public int CacheSeconds { get; set; }

        public override void OnEntry(MethodExecutionArgs args)
        {
            Invocation inv = new Invocation() { InvocationTarget = args.Instance, Method = args.Method };

            if (IsInvocationValidInCache(inv, CacheSeconds))
            {
                args.ReturnValue = GetCachedValue(inv);
                args.FlowBehavior = FlowBehavior.Return;
            }
        }

        public override void OnSuccess(MethodExecutionArgs args)
        {
            Invocation inv = new Invocation() { InvocationTarget = args.Instance, Method = args.Method };

            CacheInvocationValue(inv, args.ReturnValue);
        }

        [NonSerialized]
        private static Dictionary<Invocation, TimedValue<object>> cachedInvocations = new Dictionary<Invocation, TimedValue<object>>();

        private static void CacheInvocationValue(Invocation inv, object p)
        {
            if (cachedInvocations.ContainsKey(inv))
            {
                cachedInvocations.Remove(inv);
            }
            cachedInvocations.Add(inv, new TimedValue<object>() { Value = p, When = DateTime.Now });
        }

        private static object GetCachedValue(Invocation inv)
        {
            if (cachedInvocations.ContainsKey(inv))
            {
                return cachedInvocations[inv].Value;
            }
            else
            {
                return null;
            }
        }

        private static bool IsInvocationValidInCache(Invocation inv, int cacheSeconds)
        {
            return
                cachedInvocations.ContainsKey(inv) &&
                DateTime.Now < cachedInvocations[inv].When.AddSeconds(cacheSeconds);
        }
    }
}
